import typing
import os

from modules.moc3_reader import Moc3Reader
from modules import vector_util


class MOC3Accessor:
    def __init__(self, file: str):
        self._filename = os.path.basename(file)
        self.moc3_reader = Moc3Reader(file)
        self._current_idx = 0
        self._current_vertex = 0
        self._current_roll = 0

        self._moc3_vertex_cnt = self.moc3_reader.read_chunk_named('ArtMeshes.drawableVertexCount')
        self._moc3_uv_idx = self.moc3_reader.read_chunk_named('ArtMeshes.uvIndexStart')
        self._moc3_uv_xy = self.moc3_reader.read_chunk_named('UVs.xy', _format='<f')
        self._moc3_drawable_start = self.moc3_reader.read_chunk_named('ArtMeshes.drawableIndexStart')
        self._moc3_drawable_cnt = self.moc3_reader.read_chunk_named('ArtMeshes.drawableIndexCount')
        self._moc3_drawable_index = self.moc3_reader.read_chunk_named('DrawableIndexes.index', unit_size=2, _format='<H')

        self._moc3_kf_idx = self.moc3_reader.read_chunk_named('ArtMeshes.keyformIndex')
        self._moc3_kf_cnt = self.moc3_reader.read_chunk_named('ArtMeshes.keyformCount')
        self._moc3_kf_idx2pos = self.moc3_reader.read_chunk_named('ArtMeshKeyforms.keyformPositionIndex')
        self._moc3_kf_data = self.moc3_reader.read_chunk_named('KeyformPosition.xy', _format='<f')

        self._moc3_mesh_name = [x.rstrip(b'\0').decode('utf-8') for x in self.moc3_reader.read_chunk_named_bytes('ArtMeshes.id', 64)]
        self._moc3_mesh_texture = self.moc3_reader.read_chunk_named('ArtMeshes.texture')

        self._canvas_structure = self.moc3_reader.read_canvas_structure()
        self._canvas_size = (self._canvas_structure['width'], self._canvas_structure['height'])
        self._canvas_origin = (self._canvas_structure['originX'], self._canvas_structure['originY'])
        self._canvas_ppu = self._canvas_structure['pixelsPerUnit']

        self._uv_size = (2048, 2048)

        self._idx_max = len(self._moc3_vertex_cnt) - 1

        self._base_idx_list = [-1] * 3

    def get_file_name(self):
        return self._filename

    def set_lock_base(self, idx, value):
        self._base_idx_list[min(idx, 2)] = value
        print(self._base_idx_list)

    def reset_lock_base(self):
        self._base_idx_list = [-1] * 3

    def get_lock_base(self, idx):
        return self._base_idx_list[min(idx, 2)]

    def get_corresponding_pos(self) -> typing.Tuple[float, float]:
        # 锁定基向量后，获取当前uv在对应基向量上的坐标
        if any(_b < 0 for _b in self._base_idx_list):
            return self.get_current_pos()
        vc = self._moc3_vertex_cnt[self._current_idx]
        base_idx_list_fix = [max(0, min(_b, vc - 1)) for _b in self._base_idx_list]
        uv = self.get_current_uv()
        base_uv_list = [self.get_uv_by_vertex(i) for i in base_idx_list_fix]
        base_pos_list = [self.get_pos_by_vertex(i) for i in base_idx_list_fix]
        return vector_util.transform_base(base_uv_list, base_pos_list, uv)

    def update_corresponding_pos(self):
        self.set_current_pos(*self.get_corresponding_pos())

    def get_corresponding_uv(self) -> typing.Tuple[float, float]:
        # 锁定基向量后，获取当前pos在对应基向量上的坐标
        if any(_b < 0 for _b in self._base_idx_list):
            return self.get_current_uv()
        vc = self._moc3_vertex_cnt[self._current_idx]
        base_idx_list_fix = [max(0, min(_b, vc - 1)) for _b in self._base_idx_list]
        pos = self.get_current_pos()
        base_pos_list = [self.get_pos_by_vertex(i) for i in base_idx_list_fix]
        base_uv_list = [self.get_uv_by_vertex(i) for i in base_idx_list_fix]
        return vector_util.transform_base(base_pos_list, base_uv_list, pos)

    def update_corresponding_uv(self):
        self.set_current_uv(*self.get_corresponding_uv())

    def access_moc3_data(self, name: str):
        # access to massive moc3 data vars via reflection
        # eg. name="_moc3_vertex_cnt"
        if name.startswith("_moc3") and name in dir(self):
            return self.__getattribute__(name)
        raise RuntimeError("invalid name: " + name)

    def get_current_mesh_name(self):
        return self._moc3_mesh_name[self._current_idx]

    def get_current_idx(self):
        return self._current_idx

    def get_current_vertex(self):
        return self._current_vertex

    def moveto_idx(self, idx: int) -> int:
        self._current_idx = max(0, min(idx, self._idx_max))
        self._current_vertex = 0
        self._current_roll = 0
        return self._current_idx

    def moveto_vertex(self, vertex: int) -> int:
        vc = self._moc3_vertex_cnt[self._current_idx]
        self._current_vertex = max(0, min(vertex, vc - 1))
        return self._current_vertex

    def moveto_roll(self, roll: int) -> int:
        kf = self._moc3_kf_cnt[self._current_idx]
        self._current_roll = max(0, min(roll, kf - 1))
        return self._current_roll

    def get_current_roll(self) -> int:
        return self._current_roll

    def get_current_uv_raw(self) -> typing.Tuple[float, float]:
        return self.get_uv_raw_by_vertex(self._current_vertex)

    def get_uv_raw_by_vertex(self, vertex: int):
        uvi = self._moc3_uv_idx[self._current_idx]
        _x = self._moc3_uv_xy[uvi + vertex * 2]
        _y = self._moc3_uv_xy[uvi + vertex * 2 + 1]
        return _x, _y

    def get_current_uv(self) -> typing.Tuple[float, float]:
        return self.get_uv_by_vertex(self._current_vertex)

    def get_uv_by_vertex(self, vertex: int):
        _x, _y = self.get_uv_raw_by_vertex(vertex)
        return _x * self._uv_size[0], (1.0 - _y) * self._uv_size[1]

    def get_current_pos_raw(self) -> typing.Tuple[float, float]:
        return self.get_pos_raw_by_vertex(self._current_vertex)

    def get_pos_raw_by_vertex(self, vertex: int):
        pok = self._moc3_kf_idx[self._current_idx] + self._current_roll
        poi = self._moc3_kf_idx2pos[pok]
        _x = self._moc3_kf_data[poi + vertex * 2]
        _y = self._moc3_kf_data[poi + vertex * 2 + 1]
        return _x, _y

    def get_current_pos(self):
        return self.get_pos_by_vertex(self._current_vertex)

    def get_pos_by_vertex(self, vertex: int):
        _x, _y = self.get_pos_raw_by_vertex(vertex)
        return _x + self._canvas_size[0], self._canvas_size[1] - _y

    def move_current_uv_art_mesh(self, dx: float, dy: float):
        """
        整体移动当前ArtMesh的UV坐标
        :param dx: 移动的x坐标
        :param dy: 移动的y坐标
        :return: None
        """
        uvi = self._moc3_uv_idx[self._current_idx]
        for i in range(self._moc3_vertex_cnt[self._current_idx]):
            _x = self._moc3_uv_xy[uvi + i * 2]
            _y = self._moc3_uv_xy[uvi + i * 2 + 1]
            self._moc3_uv_xy[uvi + i * 2] = _x + dx / self._uv_size[0]
            self._moc3_uv_xy[uvi + i * 2 + 1] = _y - dy / self._uv_size[1]

    def move_current_pos_art_mesh(self, dx: float, dy: float):
        """
        整体移动当前ArtMesh的位置坐标
        :param dx: 移动的x坐标
        :param dy: 移动的y坐标
        :return: None
        """
        pok = self._moc3_kf_idx[self._current_idx] + self._current_roll
        poi = self._moc3_kf_idx2pos[pok]
        for i in range(self._moc3_vertex_cnt[self._current_idx]):
            _x = self._moc3_kf_data[poi + i * 2]
            _y = self._moc3_kf_data[poi + i * 2 + 1]
            self._moc3_kf_data[poi + i * 2] = _x + dx
            self._moc3_kf_data[poi + i * 2 + 1] = _y - dy

    def set_current_uv(self, x: float, y: float):
        self.set_current_uv_raw(x / self._uv_size[0], 1 - y / self._uv_size[1])

    def set_current_uv_raw(self, x: float, y: float):
        uvi = self._moc3_uv_idx[self._current_idx]
        self._moc3_uv_xy[uvi + self._current_vertex * 2] = x
        self._moc3_uv_xy[uvi + self._current_vertex * 2 + 1] = y

    def set_current_pos(self, x: float, y: float):
        self.set_current_pos_raw(x - self._canvas_size[0], self._canvas_size[1] - y)

    def set_current_pos_raw(self, x: float, y: float):
        pok = self._moc3_kf_idx[self._current_idx] + self._current_roll
        poi = self._moc3_kf_idx2pos[pok]
        self._moc3_kf_data[poi + self._current_vertex * 2] = x
        self._moc3_kf_data[poi + self._current_vertex * 2 + 1] = y

    def get_uv_size(self):
        return self._uv_size

    def set_uv_size(self, width: int, height: int):
        self._uv_size = (width, height)

    def get_canvas_size(self):
        return self._canvas_size

    def get_canvas_origin(self):
        return self._canvas_origin

    def get_current_roll_max(self) -> int:
        return self._moc3_kf_cnt[self._current_idx]

    def set_current_texture_idx(self, idx: int):
        self._moc3_mesh_texture[self._current_idx] = idx

    def copy_roll(self, src_roll: int, dst_roll: int):
        # 复制一组mesh坐标，从roll=src_idx复制到roll=dst_idx
        if src_roll == dst_roll:
            return
        max_roll = self._moc3_kf_cnt[self._current_idx]
        if src_roll < 0 or src_roll >= max_roll or dst_roll < 0 or dst_roll >= max_roll:
            print(f"Failed to copy roll: invalid roll index {src_roll} -> {dst_roll}")
            return

        src_pok = self._moc3_kf_idx[self._current_idx] + src_roll
        src_poi = self._moc3_kf_idx2pos[src_pok]
        dst_pok = self._moc3_kf_idx[self._current_idx] + dst_roll
        dst_poi = self._moc3_kf_idx2pos[dst_pok]

        self._moc3_kf_data[dst_poi:dst_poi+self._moc3_vertex_cnt[self._current_idx]*2] = self._moc3_kf_data[src_poi:src_poi+self._moc3_vertex_cnt[self._current_idx]*2]
        # for i in range(self._moc3_vertex_cnt[self._current_idx]):
        #     _x = self._moc3_kf_data[src_poi + i * 2]
        #     _y = self._moc3_kf_data[src_poi + i * 2 + 1]
        #     self._moc3_kf_data[dst_poi + i * 2] = _x
        #     self._moc3_kf_data[dst_poi + i * 2 + 1] = _y


    def save(self):
        self.moc3_reader.write_back_uv(self._moc3_uv_xy)
        self.moc3_reader.write_back_pos(self._moc3_kf_data)
        self.moc3_reader.write_back_texture_idx(self._moc3_mesh_texture)
        self.moc3_reader.re_open()

    def save_as(self, file: str):
        self.moc3_reader.save_as(file, self._moc3_uv_xy, self._moc3_kf_data, self._moc3_mesh_texture)
